// 版权所有2009年围棋作者。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package walk

import (
	"encoding/binary"
	"go/constant"

	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/reflectdata"
	"cmd/compile/internal/ssagen"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/internal/sys"
)

// walkConv遍历一个OCONV或OCONVNOP（但不是oconvice）节点。
func walkConv(n *ir.ConvExpr, init *ir.Nodes) ir.Node {
	n.X = walkExpr(n.X, init)
	if n.Op() == ir.OCONVNOP && n.Type() == n.X.Type() {
		return n.X
	}
	if n.Op() == ir.OCONVNOP && ir.ShouldCheckPtr(ir.CurFunc, 1) {
		if n.Type().IsPtr() && n.X.Type().IsUnsafePtr() { // 不安全。指向*T的指针
			return walkCheckPtrAlignment(n, init, nil)
		}
		if n.Type().IsUnsafePtr() && n.X.Type().IsUintptr() { // uintptr到不安全的.Pointer
			return walkCheckPtrArithmetic(n, init)
		}
	}
	param, result := rtconvfn(n.X.Type(), n.Type())
	if param == types.Txxx {
		return n
	}
	fn := types.BasicTypeNames[param] + "to" + types.BasicTypeNames[result]
	return typecheck.Conv(mkcall(fn, types.Types[result], init, typecheck.Conv(n.X, types.Types[param])), n.Type())
}

// walkConvInterface遍历OConvFace节点。
func walkConvInterface(n *ir.ConvExpr, init *ir.Nodes) ir.Node {
	n.X = walkExpr(n.X, init)

	fromType := n.X.Type()
	toType := n.Type()

	if !fromType.IsInterface() && !ir.IsBlank(ir.CurFunc.Nname) { // 跳过未命名的函数（func(（））
		reflectdata.MarkTypeUsedInInterface(fromType, ir.CurFunc.LSym)
	}

	// typeword生成接口值的类型字。
	typeword := func() ir.Node {
		if toType.IsEmptyInterface() {
			return reflectdata.TypePtr(fromType)
		}
		return reflectdata.ITabAddr(fromType, toType)
	}

	// 当T为指针形状时，将convT2E或convT2I优化为两个字的副本。
	if types.IsDirectIface(fromType) {
		l := ir.NewBinaryExpr(base.Pos, ir.OEFACE, typeword(), n.X)
		l.SetType(toType)
		l.SetTypecheck(n.Typecheck())
		return l
	}

	// 对于T不是指针形状的许多情况，优化convT2{E，I}，
	// 通过使用与n.Left相同的现有可寻址值
	// 或者在堆栈上创建一个。
	var value ir.Node
	switch {
	case fromType.Size() == 0:
		// n、 左边是零号。使用zerobase。
		cheapExpr(n.X, init) // 评估左心室的副作用。见第19246期。
		value = ir.NewLinksymExpr(base.Pos, ir.Syms.Zerobase, types.Types[types.TUINTPTR])
	case fromType.IsBoolean() || (fromType.Size() == 1 && fromType.IsInteger()):
		// n、 左边是一个布尔/字节。在little endian上使用staticuint64s[n.Left*8]
		// 和大端上的staticuint64s[n.左*8+7]。
		n.X = cheapExpr(n.X, init)
		// byteindex将n.向左加宽，这样乘法就不会溢出。
		index := ir.NewBinaryExpr(base.Pos, ir.OLSH, byteindex(n.X), ir.NewInt(3))
		if ssagen.Arch.LinkArch.ByteOrder == binary.BigEndian {
			index = ir.NewBinaryExpr(base.Pos, ir.OADD, index, ir.NewInt(7))
		}
		// 实际类型是[256]uint64，但我们使用[256*8]uint8，因此我们可以
		// 单个字节。
		staticuint64s := ir.NewLinksymExpr(base.Pos, ir.Syms.Staticuint64s, types.NewArray(types.Types[types.TUINT8], 256*8))
		xe := ir.NewIndexExpr(base.Pos, staticuint64s, index)
		xe.SetBounded(true)
		value = xe
	case n.X.Op() == ir.ONAME && n.X.(*ir.Name).Class == ir.PEXTERN && n.X.(*ir.Name).Readonly():
		// n、 左边是一个只读全局文件；直接使用它。
		value = n.X
	case !fromType.IsInterface() && n.Esc() == ir.EscNone && fromType.Width <= 1024:
		// n、 左派无法逃脱。使用初始化为n的临时堆栈。
		value = typecheck.Temp(fromType)
		init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, value, n.X)))
	}

	if value != nil {
		// 值与n相同。
		// 直接构造接口：{type/itab，&value}。
		l := ir.NewBinaryExpr(base.Pos, ir.OEFACE, typeword(), typecheck.Expr(typecheck.NodAddr(value)))
		l.SetType(toType)
		l.SetTypecheck(n.Typecheck())
		return l
	}

	// 实现接口到空接口的转换。
	// tmp=i.itab
	// 如果tmp！=零{
	// tmp=tmp.type
	// }
	// e=iface{tmp，i.data}
	if toType.IsEmptyInterface() && fromType.IsInterface() && !fromType.IsEmptyInterface() {
		// 评估输入接口。
		c := typecheck.Temp(fromType)
		init.Append(ir.NewAssignStmt(base.Pos, c, n.X))

		// 从接口中取出itab。
		tmp := typecheck.Temp(types.NewPtr(types.Types[types.TUINT8]))
		init.Append(ir.NewAssignStmt(base.Pos, tmp, typecheck.Expr(ir.NewUnaryExpr(base.Pos, ir.OITAB, c))))

		// 从itab中获取类型。
		nif := ir.NewIfStmt(base.Pos, typecheck.Expr(ir.NewBinaryExpr(base.Pos, ir.ONE, tmp, typecheck.NodNil())), nil, nil)
		nif.Body = []ir.Node{ir.NewAssignStmt(base.Pos, tmp, itabType(tmp))}
		init.Append(nif)

		// 建立结果。
		e := ir.NewBinaryExpr(base.Pos, ir.OEFACE, tmp, ifaceData(n.Pos(), c, types.NewPtr(types.Types[types.TUINT8])))
		e.SetType(toType) // 手动分配类型，typecheck不理解OEFACE。
		e.SetTypecheck(1)
		return e
	}

	fnname, argType, needsaddr := convFuncName(fromType, toType)

	if !needsaddr && !fromType.IsInterface() {
		// 使用只返回数据指针的专用转换例程。
		// ptr=convT2X（val）
		// e=iface{typ/tab，ptr}
		fn := typecheck.LookupRuntime(fnname)
		types.CalcSize(fromType)

		arg := n.X
		switch {
		case fromType == argType:
			// 已使用正确的类型，无需执行任何操作
		case fromType.Kind() == argType.Kind(),
			fromType.IsPtrShaped() && argType.IsPtrShaped():
			// 可以直接转换（例如，将命名类型转换为基础类型，或将一个指针转换为另一个指针）
			arg = ir.NewConvExpr(n.Pos(), ir.OCONVNOP, argType, arg)
		case fromType.IsInteger() && argType.IsInteger():
			// 可直接转换（如int32到uint32）
			arg = ir.NewConvExpr(n.Pos(), ir.OCONV, argType, arg)
		default:
			// 不安全的强制转换内存
			arg = copyExpr(arg, arg.Type(), init)
			var addr ir.Node = typecheck.NodAddr(arg)
			addr = ir.NewConvExpr(n.Pos(), ir.OCONVNOP, argType.PtrTo(), addr)
			arg = ir.NewStarExpr(n.Pos(), addr)
			arg.SetType(argType)
		}

		call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, nil)
		call.Args = []ir.Node{arg}
		e := ir.NewBinaryExpr(base.Pos, ir.OEFACE, typeword(), safeExpr(walkExpr(typecheck.Expr(call), init), init))
		e.SetType(toType)
		e.SetTypecheck(1)
		return e
	}

	var tab ir.Node
	if fromType.IsInterface() {
		// convI2I
		tab = reflectdata.TypePtr(toType)
	} else {
		// convT2x
		tab = typeword()
	}

	v := n.X
	if needsaddr {
		// 较大或未知大小的类型通过引用传递。
		// Orderexpr将n.Left安排为所有人的临时订单
		// 它可以看到的转换。接口的比较
		// 使用非接口，尤其是在打开接口值时
		// 对于非接口情况，order.stmt不可见，因此
		// 必须依靠在这里分配临时工。
		if !ir.IsAddressable(v) {
			v = copyExpr(v, v.Type(), init)
		}
		v = typecheck.NodAddr(v)
	}

	types.CalcSize(fromType)
	fn := typecheck.LookupRuntime(fnname)
	fn = typecheck.SubstArgTypes(fn, fromType, toType)
	types.CalcSize(fn.Type())
	call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, nil)
	call.Args = []ir.Node{tab, v}
	return walkExpr(typecheck.Expr(call), init)
}

// WalkByteSruneStoring遍历OBYTES2STR或ORUNES2STR节点。
func walkBytesRunesToString(n *ir.ConvExpr, init *ir.Nodes) ir.Node {
	a := typecheck.NodNil()
	if n.Esc() == ir.EscNone {
		// 为堆栈上的字符串创建临时缓冲区。
		a = stackBufAddr(tmpstringbufsize, types.Types[types.TUINT8])
	}
	if n.Op() == ir.ORUNES2STR {
		// slicerunetostring（*[32]字节，[]符文）字符串
		return mkcall("slicerunetostring", n.Type(), init, a, n.X)
	}
	// slicebytetostring（*[32]字节，ptr*字节，n int）字符串
	n.X = cheapExpr(n.X, init)
	ptr, len := backingArrayPtrLen(n.X)
	return mkcall("slicebytetostring", n.Type(), init, a, ptr, len)
}

// walkBytesToStringTemp遍历OBYTES2STRTMP节点。
func walkBytesToStringTemp(n *ir.ConvExpr, init *ir.Nodes) ir.Node {
	n.X = walkExpr(n.X, init)
	if !base.Flag.Cfg.Instrumenting {
		// 让后端直接处理OBYTES2STRTMP
		// 避免对SliceByteToStringMP的函数调用。
		return n
	}
	// slicebytetostringtmp（ptr*字节，n int）字符串
	n.X = cheapExpr(n.X, init)
	ptr, len := backingArrayPtrLen(n.X)
	return mkcall("slicebytetostringtmp", n.Type(), init, ptr, len)
}

// walkRuneToString遍历ORUNESTR节点。
func walkRuneToString(n *ir.ConvExpr, init *ir.Nodes) ir.Node {
	a := typecheck.NodNil()
	if n.Esc() == ir.EscNone {
		a = stackBufAddr(4, types.Types[types.TUINT8])
	}
	// intstring（*[4]字节，符文）
	return mkcall("intstring", n.Type(), init, a, typecheck.Conv(n.X, types.Types[types.TINT64]))
}

// walkStringToBytes遍历ost2Bytes节点。
func walkStringToBytes(n *ir.ConvExpr, init *ir.Nodes) ir.Node {
	s := n.X
	if ir.IsConst(s, constant.String) {
		sc := ir.StringVal(s)

		// 分配一个大小正确的[n]字节。
		t := types.NewArray(types.Types[types.TUINT8], int64(len(sc)))
		var a ir.Node
		if n.Esc() == ir.EscNone && len(sc) <= int(ir.MaxImplicitStackVarSize) {
			a = stackBufAddr(t.NumElem(), t.Elem())
		} else {
			types.CalcSize(t)
			a = ir.NewUnaryExpr(base.Pos, ir.ONEW, nil)
			a.SetType(types.NewPtr(t))
			a.SetTypecheck(1)
			a.MarkNonNil()
		}
		p := typecheck.Temp(t.PtrTo()) // *[n] 字节
		init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, p, a)))

		// 从静态字符串数据复制到[n]字节。
		if len(sc) > 0 {
			as := ir.NewAssignStmt(base.Pos, ir.NewStarExpr(base.Pos, p), ir.NewStarExpr(base.Pos, typecheck.ConvNop(ir.NewUnaryExpr(base.Pos, ir.OSPTR, s), t.PtrTo())))
			appendWalkStmt(init, as)
		}

		// 将[n]字节切片为[]字节。
		slice := ir.NewSliceExpr(n.Pos(), ir.OSLICEARR, p, nil, nil, nil)
		slice.SetType(n.Type())
		slice.SetTypecheck(1)
		return walkExpr(slice, init)
	}

	a := typecheck.NodNil()
	if n.Esc() == ir.EscNone {
		// 为堆栈上的切片创建临时缓冲区。
		a = stackBufAddr(tmpstringbufsize, types.Types[types.TUINT8])
	}
	// stringtoslicebyte（*32[字节]，字符串）[]字节
	return mkcall("stringtoslicebyte", n.Type(), init, a, typecheck.Conv(s, types.Types[types.TSTRING]))
}

// walkStringToBytesTemp遍历OST2ByteStmp节点。
func walkStringToBytesTemp(n *ir.ConvExpr, init *ir.Nodes) ir.Node {
	// []创建切片的字节（字符串）转换
	// 指的是实际的字符串字节。
	// 此转换稍后由后端和
	// 仅供内部编译器优化使用
	// 知道切片不会发生变异。
	// 今天唯一的情况是：
	// 对于i，c:=范围[]字节（字符串）
	n.X = walkExpr(n.X, init)
	return n
}

// walkStringToRunes遍历OSTR2RUNES节点。
func walkStringToRunes(n *ir.ConvExpr, init *ir.Nodes) ir.Node {
	a := typecheck.NodNil()
	if n.Esc() == ir.EscNone {
		// 为堆栈上的切片创建临时缓冲区。
		a = stackBufAddr(tmpstringbufsize, types.Types[types.TINT32])
	}
	// stringtoslicerune（*[32]符文，字符串）[]符文
	return mkcall("stringtoslicerune", n.Type(), init, a, typecheck.Conv(n.X, types.Types[types.TSTRING]))
}

// convFuncName为接口转换生成运行时函数名。
// 它还返回运行时函数采用的参数类型，以及
// 函数是否需要按地址显示数据。
// 并非所有的名字都可能。例如，我们从不生成convE2E或convE2I。
func convFuncName(from, to *types.Type) (fnname string, argType *types.Type, needsaddr bool) {
	tkind := to.Tie()
	switch from.Tie() {
	case 'I':
		if tkind == 'I' {
			return "convI2I", types.Types[types.TINTER], false
		}
	case 'T':
		switch {
		case from.Size() == 2 && from.Align == 2:
			return "convT16", types.Types[types.TUINT16], false
		case from.Size() == 4 && from.Align == 4 && !from.HasPointers():
			return "convT32", types.Types[types.TUINT32], false
		case from.Size() == 8 && from.Align == types.Types[types.TUINT64].Align && !from.HasPointers():
			return "convT64", types.Types[types.TUINT64], false
		}
		if sc := from.SoleComponent(); sc != nil {
			switch {
			case sc.IsString():
				return "convTstring", types.Types[types.TSTRING], false
			case sc.IsSlice():
				return "convTslice", types.NewSlice(types.Types[types.TUINT8]), false // 元素类型不重要
			}
		}

		switch tkind {
		case 'E':
			if !from.HasPointers() {
				return "convT2Enoptr", types.Types[types.TUNSAFEPTR], true
			}
			return "convT2E", types.Types[types.TUNSAFEPTR], true
		case 'I':
			if !from.HasPointers() {
				return "convT2Inoptr", types.Types[types.TUNSAFEPTR], true
			}
			return "convT2I", types.Types[types.TUNSAFEPTR], true
		}
	}
	base.Fatalf("unknown conv func %c2%c", from.Tie(), to.Tie())
	panic("unreachable")
}

// rtconvfn返回将由
// 运行时函数将src类型转换为dst类型。运行时函数
// 名称可以从返回类型的名称派生。
// None
// 如果不需要这样的函数，则返回（txx，txx）。
func rtconvfn(src, dst *types.Type) (param, result types.Kind) {
	if ssagen.Arch.SoftFloat {
		return types.Txxx, types.Txxx
	}

	switch ssagen.Arch.LinkArch.Family {
	case sys.ARM, sys.MIPS:
		if src.IsFloat() {
			switch dst.Kind() {
			case types.TINT64, types.TUINT64:
				return types.TFLOAT64, dst.Kind()
			}
		}
		if dst.IsFloat() {
			switch src.Kind() {
			case types.TINT64, types.TUINT64:
				return src.Kind(), types.TFLOAT64
			}
		}

	case sys.I386:
		if src.IsFloat() {
			switch dst.Kind() {
			case types.TINT64, types.TUINT64:
				return types.TFLOAT64, dst.Kind()
			case types.TUINT32, types.TUINT, types.TUINTPTR:
				return types.TFLOAT64, types.TUINT32
			}
		}
		if dst.IsFloat() {
			switch src.Kind() {
			case types.TINT64, types.TUINT64:
				return src.Kind(), types.TFLOAT64
			case types.TUINT32, types.TUINT, types.TUINTPTR:
				return types.TUINT32, types.TFLOAT64
			}
		}
	}
	return types.Txxx, types.Txxx
}

// byteindex将字节大小的n转换为用于索引到数组中的int。
// 我们不能使用conv，因为这里允许将bool转换为int，
// 这在用户代码中是禁止的。
func byteindex(n ir.Node) ir.Node {
	// 我们不能直接从bool转换为int。
	// 虽然从int8转换为int是可能的，但它会产生
	// 负值的错误结果。
	// 将该值重新解释为无符号字节可以解决这两种情况。
	if !types.Identical(n.Type(), types.Types[types.TUINT8]) {
		n = ir.NewConvExpr(base.Pos, ir.OCONV, nil, n)
		n.SetType(types.Types[types.TUINT8])
		n.SetTypecheck(1)
	}
	n = ir.NewConvExpr(base.Pos, ir.OCONV, nil, n)
	n.SetType(types.Types[types.TINT])
	n.SetTypecheck(1)
	return n
}

func walkCheckPtrAlignment(n *ir.ConvExpr, init *ir.Nodes, count ir.Node) ir.Node {
	if !n.Type().IsPtr() {
		base.Fatalf("expected pointer type: %v", n.Type())
	}
	elem := n.Type().Elem()
	if count != nil {
		if !elem.IsArray() {
			base.Fatalf("expected array type: %v", elem)
		}
		elem = elem.Elem()
	}

	size := elem.Size()
	if elem.Alignment() == 1 && (size == 0 || size == 1 && count == nil) {
		return n
	}

	if count == nil {
		count = ir.NewInt(1)
	}

	n.X = cheapExpr(n.X, init)
	init.Append(mkcall("checkptrAlignment", nil, init, typecheck.ConvNop(n.X, types.Types[types.TUNSAFEPTR]), reflectdata.TypePtr(elem), typecheck.Conv(count, types.Types[types.TUINTPTR])))
	return n
}

func walkCheckPtrArithmetic(n *ir.ConvExpr, init *ir.Nodes) ir.Node {
	// 在下面调用cheapExpr（n，init）会导致对
	// walkExpr，这又把我们带回到这里。使用n.Checkptr
	// 防止无限循环。
	if n.CheckPtr() {
		return n
	}
	n.SetCheckPtr(true)
	defer n.SetCheckPtr(false)

	// TODO（mdempsky）：使更严格。我们只需要豁免
	// reflect.Value.Pointer和reflect.Value.UnsafeAddr。
	switch n.X.Op() {
	case ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER:
		return n
	}

	if n.X.Op() == ir.ODOTPTR && ir.IsReflectHeaderDataField(n.X) {
		return n
	}

	// 查找此操作中涉及的原始不安全指针操作数
	// 算术表达式。
	// None
	// “从偏移量中添加和减去偏移量都是有效的
	// 以这种方式使用指针。使用&^进行舍入也是有效的
	// 指针，通常用于对齐。”
	var originals []ir.Node
	var walk func(n ir.Node)
	walk = func(n ir.Node) {
		switch n.Op() {
		case ir.OADD:
			n := n.(*ir.BinaryExpr)
			walk(n.X)
			walk(n.Y)
		case ir.OSUB, ir.OANDNOT:
			n := n.(*ir.BinaryExpr)
			walk(n.X)
		case ir.OCONVNOP:
			n := n.(*ir.ConvExpr)
			if n.X.Type().IsUnsafePtr() {
				n.X = cheapExpr(n.X, init)
				originals = append(originals, typecheck.ConvNop(n.X, types.Types[types.TUNSAFEPTR]))
			}
		}
	}
	walk(n.X)

	cheap := cheapExpr(n, init)

	slice := typecheck.MakeDotArgs(types.NewSlice(types.Types[types.TUNSAFEPTR]), originals)
	slice.SetEsc(ir.EscNone)

	init.Append(mkcall("checkptrArithmetic", nil, init, typecheck.ConvNop(cheap, types.Types[types.TUNSAFEPTR]), slice))
	// TODO（khr）：将切片的备份存储标记为死。这将允许我们重新使用
	// 用于多次调用checkptrArithmetic的备份存储。

	return cheap
}
